﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics.Contracts;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

// http://stackoverflow.com/questions/1128897/wpf-datatemplate-and-binding
// TOFIX: replace orientation\resolution switches with matrix...
//        or even construct it like: "YearsListBox_" + "Vertical" + "_" + "Days"

namespace Efficiency.Controls
{
	public enum TimeLineResolution
	{
		Year,
		Month,
		Day
	}

    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class TimeLine : UserControl
	{
		// TOFIX: get it from resources
		private static readonly string[] MONTHS = { "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };

		#region Types

		public class DayEntry
		{
			#region Properties

			public int Ordinal
			{
				get;
				private set;
			}

			public IList<object> Items
			{
				get;
				private set;
			}

			#endregion // Properties

			#region Constructor

			public DayEntry(int year, int month, int ordinal)
			{
				Ordinal = ordinal;
				Items = new List<object>();
			}

			#endregion // Constructor
		}

		public class MonthEntry
		{
			#region Properties

			public string Name
			{
				get;
				private set;
			}

			public int Ordinal
			{
				get;
				private set;
			}

			public IEnumerable<DayEntry> Days
			{
				get
				{
					return _days.Values;
				}
			}

			public IList<object> Items
			{
				get;
				private set;
			}

			#endregion // Properties

			#region Constructor

			public MonthEntry(int year, int ordinal, string name)
			{
				Ordinal = ordinal;
				Name = name;
				Items = new List<object>();
			}

			#endregion // Constructor

			#region Internal Methods

			internal void AddDay(DayEntry day)
			{
				_days.Add(day.Ordinal, day);
			}

			internal DayEntry GetDay(int ordinal)
			{
				return _days[ordinal];
			}

			internal void RemoveDay(int ordinal)
			{
				_days.Remove(ordinal);
			}

			#endregion // Internal Methods

			#region Fields

			private SortedDictionary<int, DayEntry> _days = new SortedDictionary<int, DayEntry>();

			#endregion // Fields
		}

		public class YearEntry
		{
			#region Properties

			public int Year
			{
				get;
				private set;
			}

			public IEnumerable<MonthEntry> Months
			{
				get
				{
					return _months.Values;
				}
			}

			public IList<object> Items
			{
				get;
				private set;
			}

			#endregion // Properties

			#region Constructor

			public YearEntry(int year)
			{
				Year = year;
				Items = new List<object>();
			}

			#endregion // Constructor

			#region Internal Methods

			internal void AddMonth(MonthEntry month)
			{
				_months.Add(month.Ordinal, month);
			}

			internal MonthEntry GetMonth(int ordinal)
			{
				return _months[ordinal];
			}

			internal void RemoveMonth(int ordinal)
			{
				_months.Remove(ordinal);
			}

			#endregion // Internal Methods

			#region Fields

			private SortedDictionary<int, MonthEntry> _months = new SortedDictionary<int, MonthEntry>();

			#endregion // Fields
		}

		#endregion // Types

		#region Dependency Properties

		public static DependencyProperty DateMinProperty = DependencyProperty.Register("DateMin", typeof(DateTime), typeof(TimeLine),
				new FrameworkPropertyMetadata(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1), OnDateMinChanged));

		public static DependencyProperty DateMaxProperty = DependencyProperty.Register("DateMax", typeof(DateTime), typeof(TimeLine),
				new FrameworkPropertyMetadata(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month)), OnDateMaxChanged));

		public static DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(TimeLine),
				new FrameworkPropertyMetadata(Orientation.Horizontal, OnOrientationChanged));

		public static readonly DependencyProperty ResolutionProperty = DependencyProperty.Register("Resolution", typeof(TimeLineResolution), typeof(TimeLine),
				new FrameworkPropertyMetadata(TimeLineResolution.Day, OnResolutionChanged));

		public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable<object>), typeof(TimeLine),
				new FrameworkPropertyMetadata(null, OnItemsChanged));

		public static DependencyProperty BindingPropertyNameProperty = DependencyProperty.Register("BindingPropertyName", typeof(string), typeof(TimeLine),
				new FrameworkPropertyMetadata("", OnBindingPropertyNameChanged));

		public static DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(TimeLine),
				new FrameworkPropertyMetadata(null, OnItemTemplateChanged));

		#endregion // Dependency Properties

		#region Properties

		public DateTime DateMin
		{
			get
			{
				return (DateTime)GetValue(DateMinProperty);
			}
			set
			{
				SetValue(DateMinProperty, value);
			}
		}

		public DateTime DateMax
		{
			get
			{
				return (DateTime)GetValue(DateMaxProperty);
			}
			set
			{
				SetValue(DateMaxProperty, value);
			}
		}

		public Orientation Orientation
		{
			get
			{
				return (Orientation)GetValue(OrientationProperty);
			}
			set
			{
				SetValue(OrientationProperty, value);
			}
		}

		public TimeLineResolution Resolution
		{
			get
			{
				return (TimeLineResolution)GetValue(ResolutionProperty);
			}
			set
			{
				SetValue(ResolutionProperty, value);
			}
		}

		public IEnumerable<YearEntry> Years
		{
			get
			{
				return _years;
			}
		}

		public IEnumerable<object> Items
		{
			get
			{
				return GetValue(ItemsProperty) as IList<object>;
			}
			set
			{
				SetValue(ItemsProperty, value);
			}
		}

		public string BindingPropertyName
		{
			get
			{
				return GetValue(BindingPropertyNameProperty) as String;
			}
			set
			{
				SetValue(BindingPropertyNameProperty, value);
			}
		}

		public DataTemplate ItemTemplate
		{
			get
			{
				return GetValue(ItemTemplateProperty) as DataTemplate;
			}
			set
			{
				SetValue(ItemTemplateProperty, value);
			}
		}

		#endregion // Properties

		// DaysLabelsVisibility
		// MonthLabelsVisibility
		// YearsLabelsVisibility

		#region Constructor

		public TimeLine()
		{
			InitializeComponent();
			UpdateItemsList();
		}

		#endregion // Constructor

		#region Dependency Properties' Handlers

		private static void OnDateMinChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;
			Contract.Assert(control != null);

			control.UpdateItemsList();
		}

		private static void OnDateMaxChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;
			Contract.Assert(control != null);

			control.UpdateItemsList();
		}

		private static void OnOrientationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;

			Contract.Assert(control != null);

			control.UpdateTemplate();
			control.UpdateItemsList();
		}

		private static void OnResolutionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;
			Contract.Assert(control != null);

			control.UpdateTemplate();
			control.UpdateItemsList();
		}

		private static void OnItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;
			Contract.Assert(control != null);

			control.UpdateItemsList();
		}

		private static void OnBindingPropertyNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TimeLine control = sender as TimeLine;
			Contract.Assert(control != null);

			control.UpdateItemsList();
		}

		private static void OnItemTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			// ???
		}

		#endregion // Dependency Properties' Handlers

		#region Private Methods

		private void UpdateTemplate()
		{
			switch (Orientation)
			{
				case System.Windows.Controls.Orientation.Horizontal:
					uiMainScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
					uiMainScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
					break;

				case System.Windows.Controls.Orientation.Vertical:
					uiMainScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
					uiMainScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
					break;
			}

			uiMainListBox.Style = FindResource(_styles[(int)Resolution, (int)Orientation]) as Style;
		}

		private void UpdateItemsList()
		{
			UpdateTimeGrid();
			PopulateTimeGrid();
		}

		private void UpdateTimeGrid()
		{
			_years.Clear();

			for (int year = DateMin.Year; year <= DateMax.Year; ++year)
			{
				var yearEntry = new YearEntry(year);

				if ((int)Resolution > (int)TimeLineResolution.Year)
				{
					int startMonth = year == DateMin.Year ? DateMin.Month : 1;
					int endMonth = year == DateMax.Year ? DateMax.Month : 12;

					for (int month = startMonth; month <= endMonth; ++month)
					{
						var monthEntry = new MonthEntry(year, month, MONTHS[month]);

						if ((int)Resolution > (int)TimeLineResolution.Month)
						{
							int startDay = year == DateMin.Year && month == DateMin.Month ? DateMin.Day : 1;
							int endDay = year == DateMax.Year && month == DateMax.Month ? DateMax.Day : DateTime.DaysInMonth(year, month);

							for (int day = startDay; day <= endDay; ++day)
							{
								monthEntry.AddDay(new DayEntry(year, month, day));
							}
						}

						yearEntry.AddMonth(monthEntry);
					}
				}

				_years.Add(yearEntry);
			}
		}

		private void PopulateTimeGrid()
		{
			if (String.IsNullOrEmpty(BindingPropertyName) || Items == null)
			{
				return;
			}

			switch (Resolution)
			{
				case TimeLineResolution.Day:
					foreach (var item in Items)
					{
						var date = GetItemDate(item);

						if (date != null && IsInRange(date.Value))
						{
							_years[GetYearIndex(date.Value.Year)].GetMonth(date.Value.Month).GetDay(date.Value.Day).Items.Add(item);
						}
					}
					break;

				case TimeLineResolution.Month:
					foreach (var item in Items)
					{
						var date = GetItemDate(item);

						if (date != null && IsInRange(date.Value))
						{
							_years[GetYearIndex(date.Value.Year)].GetMonth(date.Value.Month).Items.Add(item);
						}
					}
					break;

				case TimeLineResolution.Year:
					foreach (var item in Items)
					{
						var date = GetItemDate(item);

						if (date != null && IsInRange(date.Value))
						{
							_years[GetYearIndex(date.Value.Year)].Items.Add(item);
						}
					}
					break;
			}
		}

		private DateTime? GetItemDate(object item)
		{
			if (String.IsNullOrEmpty(BindingPropertyName) || item == null)
			{
				return null;
			}

			var prop = item.GetType().GetProperty(BindingPropertyName);

			if (prop == null || prop.PropertyType != typeof(DateTime))
			{
				return null;
			}

			return (DateTime)prop.GetValue(item, null);
		}

		private bool IsInRange(DateTime date)
		{
			return date >= DateMin && date <= DateMax;
		}

		private int GetYearIndex(int year)
		{
			Contract.Assert(year >= DateMin.Year && year <= DateMax.Year);
			return year - DateMin.Year;
		}

		#endregion // Private Methods

		#region Fields

		private ObservableCollection<YearEntry> _years = new ObservableCollection<YearEntry>();
		private string[,] _styles = new string[,] {
			{ "YearsListBox_Horizontal_Years",  "YearsListBox_Vertical_Years"  },
			{ "YearsListBox_Horizontal_Months", "YearsListBox_Vertical_Months" },
			{ "YearsListBox_Horizontal_Days",   "YearsListBox_Vertical_Days"   } };

		#endregion // Fields
	}
}
